Перейти к основному содержимому

5.01. Справочник по React

Разработчику Архитектору

Справочник по React

Общие принципы React

React — это декларативная JavaScript-библиотека для построения пользовательских интерфейсов.
React использует компонентную архитектуру.
Каждый компонент инкапсулирует собственное состояние и поведение.
React управляет обновлениями DOM через виртуальное представление дерева элементов.
React поддерживает однонаправленный поток данных: данные передаются от родительских компонентов к дочерним через props.
React предоставляет систему жизненного цикла через хуки в функциональных компонентах.
React совместим с серверным рендерингом и гидратацией.
React работает в строгом режиме разработки для выявления потенциальных проблем.
React поддерживает Concurrent Rendering для улучшения отзывчивости интерфейса.


Основные понятия

Элемент React

Элемент React — это легковесное описание того, что должно появиться на экране.
Элемент создаётся вызовом React.createElement(type, props, ...children) или синтаксисом JSX.
Элемент неизменяем.
Элемент содержит тип (type), свойства (props) и потомков (children).

Пример:

const element = <h1 className="title">Привет, мир!</h1>;

Компонент

Компонент — это функция или класс, возвращающий React-элемент.
Функциональный компонент — это обычная JavaScript-функция, принимающая props и возвращающая JSX.
Классовый компонент — это класс, наследующий React.Component, с методом render().
Современный React рекомендует использовать функциональные компоненты с хуками.

Пример функционального компонента:

function Greeting({ name }) {
return <p>Привет, {name}!</p>;
}

Props

Props — это входные данные компонента.
Props передаются как атрибуты в JSX.
Props являются неизменяемыми внутри компонента.
Props могут содержать примитивы, объекты, массивы, функции, React-элементы и JSX-фрагменты.
Props поддерживают деструктуризацию.

Пример:

<UserCard name="Алиса" age={30} />

State

State — это внутренние данные компонента, которые могут изменяться со временем.
State управляется через хук useState в функциональных компонентах.
State инициализируется при первом рендере.
Обновление state запускает повторный рендер компонента.
State должен обновляться только через функцию-сеттер, возвращаемую useState.

Пример:

const [count, setCount] = useState(0);

Children

Children — это специальное свойство props.children, содержащее содержимое между открывающим и закрывающим тегами компонента.
Children может быть строкой, числом, массивом, React-элементом или null.
Children доступны через props.children или деструктуризацию.

Пример:

<Panel>
<p>Это дочерний элемент</p>
</Panel>

Хуки React

Хуки — это функции, позволяющие использовать состояние и другие возможности React в функциональных компонентах.

useState

useState добавляет локальное состояние в функциональный компонент.
useState принимает начальное значение и возвращает пару: текущее значение и функцию для его обновления.
Функция обновления state принимает новое значение или функцию, вычисляющую новое значение на основе предыдущего.
Обновление state с тем же значением не вызывает повторного рендера.

Сигнатура:

const [state, setState] = useState(initialValue);

useEffect

useEffect выполняет побочные эффекты после рендера компонента.
useEffect заменяет методы жизненного цикла componentDidMount, componentDidUpdate, componentWillUnmount.
useEffect принимает функцию и необязательный массив зависимостей.
Если массив зависимостей пуст ([]), эффект выполняется один раз после монтирования.
Если массив зависимостей содержит значения, эффект выполняется при изменении любого из них.
Функция, возвращаемая из useEffect, выполняется при размонтировании компонента или перед следующим вызовом эффекта.

Сигнатура:

useEffect(() => {
// код эффекта
return () => {
// очистка
};
}, [dependencies]);

useContext

useContext подписывается на изменения контекста React.
useContext принимает объект контекста, созданный с помощью React.createContext.
При изменении значения контекста все компоненты, использующие useContext, перерисовываются.

Сигнатура:

const value = useContext(MyContext);

useReducer

useReducer управляет сложным состоянием через редьюсер.
useReducer принимает редьюсер-функцию и начальное состояние, возвращает текущее состояние и функцию dispatch.
Редьюсер имеет сигнатуру (state, action) => newState.
useReducer предпочтителен при наличии сложной логики обновления состояния или когда состояние зависит от предыдущего.

Сигнатура:

const [state, dispatch] = useReducer(reducer, initialState);

useCallback

useCallback мемоизирует функцию между рендерами.
useCallback принимает функцию и массив зависимостей.
Возвращаемая функция сохраняется между рендерами, если зависимости не изменились.
useCallback используется для оптимизации производительности при передаче колбэков в дочерние компоненты.

Сигнатура:

const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);

useMemo

useMemo мемоизирует значение между рендерами.
useMemo принимает функцию-вычисление и массив зависимостей.
Значение пересчитывается только при изменении зависимостей.
useMemo применяется для дорогостоящих вычислений.

Сигнатура:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useRef

useRef создаёт изменяемый объект с постоянным свойством .current.
useRef возвращает один и тот же объект между рендерами.
useRef используется для хранения ссылки на DOM-элемент или любого изменяемого значения, не вызывающего повторный рендер при изменении.

Сигнатура:

const ref = useRef(initialValue);

useImperativeHandle

useImperativeHandle настраивает экземпляр, предоставляемый через ref.
useImperativeHandle используется вместе с forwardRef.
Он ограничивает или расширяет методы, доступные родительскому компоненту через ref.

Сигнатура:

useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus()
}));

useLayoutEffect

useLayoutEffect работает так же, как useEffect, но синхронно после всех DOM-изменений и до отрисовки браузером.
useLayoutEffect используется для измерения макета или синхронного изменения DOM.

Сигнатура:

useLayoutffect(() => {
// синхронный эффект
}, [deps]);

useDebugValue

useDebugValue добавляет метку в React DevTools для пользовательских хуков.
useDebugValue не влияет на поведение приложения.

Сигнатура:

useDebugValue(customHookValue);

Встроенные компоненты и API

Fragment

Fragment группирует дочерние элементы без добавления лишнего узла в DOM.
Fragment доступен как <React.Fragment> или сокращённо <>...</>.

Пример:

<>
<li>Первый</li>
<li>Второй</li>
</>

Suspense

Suspense позволяет компонентам «ждать» асинхронные операции, такие как загрузка данных или кода.
Suspense принимает пропс fallback, который отображается во время ожидания.
Suspense работает с lazy, fetch, и другими механизмами, выбрасывающими Promise.

Пример:

<Suspense fallback={<Spinner />}>
<LazyComponent />
</Suspense>

lazy

lazy позволяет лениво загружать компоненты с помощью динамического импорта.
lazy возвращает компонент, который можно использовать внутри Suspense.

Пример:

const LazyComponent = React.lazy(() => import('./LazyComponent'));

Profiler

Profiler измеряет время рендеринга поддерева компонентов.
Profiler принимает id, onRender и необязательный report флаг.
Profiler активен только в development-сборке.

Пример:

<Profiler id="App" onRender={callback}>
<App />
</Profiler>

StrictMode

StrictMode помогает выявлять потенциальные проблемы в приложении.
StrictMode активирует дополнительные проверки и предупреждения.
StrictMode не рендерит дополнительные DOM-узлы.
StrictMode запускает некоторые хуки дважды в development для выявления побочных эффектов.

Пример:

<React.StrictMode>
<App />
</React.StrictMode>

Атрибуты и пропсы стандартных HTML-элементов в JSX

Все стандартные HTML-атрибуты доступны в JSX с некоторыми отличиями:

  • classclassName
  • forhtmlFor
  • tabindextabIndex
  • readonlyreadOnly
  • maxlengthmaxLength
  • colspancolSpan
  • rowspanrowSpan
  • srcsetsrcSet
  • accept-charsetacceptCharset

Булевы атрибуты (disabled, checked, selected, required, hidden) принимают значения true или false.

Пример:

<input type="text" readOnly={true} disabled={false} />

Специальные пропсы

key

key — это уникальный идентификатор элемента в списке.
key помогает React определять, какие элементы изменились, добавились или удалены.
key должен быть стабильным, предсказуемым и уникальным среди соседей.
key не передаётся в компонент как props.

Пример:

{items.map(item => <Item key={item.id} {...item} />)}

ref

ref предоставляет прямой доступ к DOM-элементу или экземпляру компонента.
ref может быть объектом, созданным с помощью useRef, или колбэком.
ref не передаётся как обычный prop.
Для передачи ref в функциональный компонент используется forwardRef.

Пример:

const inputRef = useRef();
return <input ref={inputRef} />;

Настройки и параметры сборки

React DOM Client

react-dom/client предоставляет API для рендеринга на клиенте.

  • createRoot(container) — создаёт корневой узел для Concurrent Mode.
  • root.render(element) — рендерит React-элемент в корневой контейнер.
  • root.unmount() — удаляет дерево React из контейнера.

Пример:

import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

Гидратация

hydrateRoot используется для гидратации серверного рендера.

Пример:

hydrateRoot(document.getElementById('root'), <App />);

Context API

React.createContext

React.createContext создаёт объект контекста.
Объект контекста содержит два компонента: Provider и Consumer.
Provider передаёт значение контекста всем дочерним компонентам, подписанным на него.
Consumer позволяет использовать значение контекста без хуков (в классовых компонентах).

Сигнатура:

const MyContext = React.createContext(defaultValue);

Context.Provider

Provider — это компонент, оборачивающий поддерево, которому нужно доступ к значению контекста.
Provider принимает пропс value, который становится текущим значением контекста для всех потребителей внутри.
Изменение value вызывает повторный рендер всех подписанных компонентов.

Пример:

<MyContext.Provider value={theme}>
<App />
</MyContext.Provider>

Context.Consumer

Consumer — это компонент, принимающий функцию-рендерер, которая получает текущее значение контекста.
Consumer используется в классовых компонентах или когда хуки недоступны.

Пример:

<MyContext.Consumer>
{value => <div>Тема: {value}</div>}
</MyContext.Consumer>

useContext

useContext(MyContext) возвращает текущее значение контекста.
Компонент автоматически перерисовывается при изменении значения контекста.
useContext заменяет необходимость в Consumer.


Управление формами

Контролируемые компоненты

Контролируемый компонент — это элемент формы, значение которого управляется через React-состояние.
Значение элемента задаётся через пропс value (для <input>, <textarea>, <select>).
Изменение значения обрабатывается через обработчик onChange.

Пример:

const [name, setName] = useState('');
return <input value={name} onChange={e => setName(e.target.value)} />;

Неконтролируемые компоненты

Неконтролируемый компонент — это элемент формы, значение которого управляется DOM напрямую.
Для доступа к значению используется ref.
Неконтролируемые компоненты применяются редко, чаще в интеграциях со сторонними библиотеками.

Пример:

const inputRef = useRef();
const handleSubmit = () => console.log(inputRef.current.value);
return <input ref={inputRef} />;

Работа с несколькими полями

Для управления множеством полей используется один обработчик с динамическим ключом.
Каждое поле должно иметь атрибут name, совпадающий с ключом в объекте состояния.

Пример:

const [form, setForm] = useState({ name: '', email: '' });
const handleChange = (e) => {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
};

Валидация

Валидация реализуется через дополнительное состояние ошибок.
Ошибки могут проверяться в реальном времени (onChange) или при отправке (onSubmit).
Результаты валидации отображаются рядом с полями формы.

Пример:

const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!email.includes('@')) newErrors.email = 'Неверный email';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};

Сброс формы

Сброс формы выполняется через reset() на DOM-элементе или сброс состояния в начальное значение.
При использовании контролируемых компонентов рекомендуется сбрасывать состояние программно.


Обработка событий

События в React

React использует синтетические события — кросс-браузерную обёртку над нативными событиями.
Обработчики событий регистрируются в JSX с помощью пропсов, начинающихся с on: onClick, onChange, onSubmit и т.д.
Обработчики получают экземпляр SyntheticEvent.

Распространённые типы событий

  • onClick — клик мышью
  • onChange — изменение значения формы
  • onSubmit — отправка формы
  • onKeyDown, onKeyPress, onKeyUp — клавиатурные события
  • onFocus, onBlur — фокусировка и потеря фокуса
  • onMouseMove, onMouseDown, onMouseUp — события мыши
  • onDragStart, onDrop — drag-and-drop

Предотвращение поведения по умолчанию

event.preventDefault() отменяет стандартное поведение браузера (например, отправку формы).
event.stopPropagation() останавливает всплытие события.


Жизненный цикл компонентов

Функциональные компоненты

Жизненный цикл управляется через хуки:

  • Монтирование: useEffect с пустым массивом зависимостей
  • Обновление: useEffect с зависимостями
  • Размонтирование: функция очистки в useEffect

Классовые компоненты (устаревший подход)

  • constructor() — инициализация состояния
  • render() — возврат JSX
  • componentDidMount() — после монтирования
  • componentDidUpdate(prevProps, prevState) — после обновления
  • componentWillUnmount() — перед размонтированием
  • shouldComponentUpdate(nextProps, nextState) — оптимизация рендера

Производительность

React.memo

React.memo — это HOC (Higher-Order Component), который предотвращает повторный рендер компонента, если props не изменились.
React.memo выполняет поверхностное сравнение props.
Для кастомного сравнения можно передать вторым аргументом функцию.

Пример:

const MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => {
return prevProps.id === nextProps.id;
});

useMemo и useCallback

Эти хуки предотвращают лишние вычисления и создание новых функций при каждом рендере.
Они особенно полезны при передаче колбэков и сложных объектов в дочерние компоненты, обёрнутые в React.memo.

key в списках

Правильное использование key минимизирует количество DOM-операций.
key должен быть уникальным и стабильным (не использовать index при изменении порядка).


Обработка ошибок

ErrorBoundary

ErrorBoundary — это компонент-обёртка, перехватывающий ошибки в дочернем дереве.
Реализуется только как классовый компонент с методами:

  • static getDerivedStateFromError(error) — обновляет состояние для отображения запасного UI
  • componentDidCatch(error, info) — логирует ошибку

Пример:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
logErrorToService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return <h1>Что-то пошло не так.</h1>;
}
return this.props.children;
}
}

Роутинг (на основе React Router v6)

Хотя роутинг не входит в ядро React, он является неотъемлемой частью большинства приложений.

Основные компоненты

  • BrowserRouter — обёртка для маршрутизации с использованием History API
  • Routes — контейнер для маршрутов
  • Route — определяет соответствие пути и компонента
  • Link — навигационная ссылка без перезагрузки страницы
  • NavLinkLink с поддержкой активного состояния
  • useNavigate — хук для программной навигации
  • useParams — хук для получения параметров маршрута
  • useLocation — хук для получения текущего URL

Пример:

<Routes>
<Route path="/" element={<Home />} />
<Route path="/user/:id" element={<UserProfile />} />
</Routes>

Динамические параметры

Параметры извлекаются через useParams().
Пример пути: /user/:iduseParams() возвращает { id: "123" }.

Вложенные маршруты

Вложенные маршруты определяются через children в Route.
Дочерний контент отображается в Outlet родительского компонента.


Concurrent Mode и современный рендеринг

Concurrent Rendering

Concurrent Rendering — это режим работы React, позволяющий прерывать и возобновлять процесс рендеринга.
React разбивает работу на фрагменты и уступает управление браузеру между ними.
Это повышает отзывчивость интерфейса при высокой нагрузке.
Concurrent Rendering активируется автоматически при использовании createRoot из react-dom/client.

Transition API (startTransition)

startTransition помечает обновления как «не срочные».
Такие обновления не блокируют пользовательский ввод и могут быть прерваны более приоритетными задачами.
Используется для переключения между состояниями, требующими тяжёлых вычислений или загрузки данных.

Сигнатура:

import { startTransition } from 'react';

startTransition(() => {
// обновление состояния
setSearchQuery(input);
});

useTransition

useTransition — хук, возвращающий пару: булево значение isPending и функцию startTransition.
isPending указывает, выполняется ли переход, и позволяет отображать индикатор загрузки.

Пример:

const [isPending, startTransition] = useTransition();

const handleSearch = (value) => {
startTransition(() => {
setSearchResults(filterData(value));
});
};

return (
<div>
{isPending && <Spinner />}
<Results data={searchResults} />
</div>
);

useDeferredValue

useDeferredValue откладывает обновление части UI, чтобы сохранить отзывчивость основного интерфейса.
Он принимает значение и возвращает «отложенную» версию этого значения.
Полезен при работе с большими списками или сложными визуализациями.

Пример:

const deferredQuery = useDeferredValue(query);
const filteredItems = useMemo(() =>
items.filter(item => item.includes(deferredQuery)),
[deferredQuery]
);

Suspense и ленивая загрузка

Suspense для кода

React.lazy и Suspense позволяют динамически загружать компоненты только при необходимости.
Это уменьшает размер начальной сборки и ускоряет первую отрисовку.

Пример:

const ProfilePage = React.lazy(() => import('./ProfilePage'));

function App() {
return (
<Suspense fallback={<Loading />}>
<ProfilePage />
</Suspense>
);
}

Suspense для данных

Suspense может работать с любым Promise, включая сетевые запросы.
Библиотеки вроде Relay, React Query (с адаптером) или собственные решения могут выбрасывать Promise при первом чтении данных.
Пока Promise не выполнится, отображается fallback.

Пример концептуальный:

function User({ id }) {
const user = resource.read(id); // выбрасывает Promise, если данные не готовы
return <h1>{user.name}</h1>;
}

Примечание: нативная поддержка Suspense для произвольных данных находится в стадии экспериментов. На практике чаще используется в связке с фреймворками (Next.js, Remix) или специализированными библиотеками.


Server Components (экспериментальная модель)

Что такое Server Components

Server Components — это компоненты, которые рендерятся на сервере и не отправляют JavaScript-код клиенту.
Они имеют доступ к базе данных, файловой системе и другим серверным ресурсам напрямую.
Server Components уменьшают размер клиентского бандла и повышают безопасность (чувствительный код остаётся на сервере).

Смешанный рендеринг

Приложение может содержать:

  • Server Components — для статического или тяжёлого контента
  • Client Components — для интерактивности, обозначаются "use client" в начале файла

Пример Client Component:

"use client";

import { useState } from 'react';

export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Передача данных

Данные из Server Components передаются в Client Components через props.
Props должны быть сериализуемыми (строки, числа, объекты без функций или классов).
Функции, refs, события передаваться не могут.


Интеграция с внешними библиотеками управления состоянием

Redux

Redux — это предсказуемый контейнер состояния для JavaScript-приложений.
Состояние хранится в едином хранилище (store).
Обновления происходят через чистые функции-редьюсеры.
В React используется через Provider и хуки useSelector, useDispatch из react-redux.

Основные элементы:

  • createStore (устарел) → заменён на configureStore из Redux Toolkit
  • slice — модульное определение редьюсера и экшенов
  • dispatch(action) — отправка действия
  • selector — функция для извлечения части состояния

Пример:

const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
dispatch(increment());

Zustand

Zustand — это минималистичная библиотека управления состоянием без контекста и провайдеров.
Состояние создаётся через create, возвращающее хук.
Zustand использует иммутабельные обновления и middleware (например, persist).

Пример:

import { create } from 'zustand';

const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));

// В компоненте:
const { count, increment } = useStore();

React Query (TanStack Query)

React Query управляет асинхронным состоянием: кэшированием, фоновыми обновлениями, повторными запросами.
Он абстрагирует работу с API и синхронизацией данных.
Основные хуки: useQuery, useMutation, useInfiniteQuery.

Пример:

const { data, isLoading, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});

Пользовательские хуки — шаблоны и примеры

useLocalStorage

Синхронизирует состояние с localStorage.

function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});

const setValue = (value) => {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
};

return [storedValue, setValue];
}

useDebounce

Откладывает выполнение до тех пор, пока значение не перестанет обновляться заданное время.

function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}

useOnClickOutside

Вызывает колбэк при клике вне указанного элемента.

function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
if (!ref.current || ref.current.contains(event.target)) return;
handler(event);
};
document.addEventListener('mousedown', listener);
return () => document.removeEventListener('mousedown', listener);
}, [ref, handler]);
}

Тестирование компонентов

React Testing Library

React Testing Library — это инструмент для тестирования компонентов в условиях, приближенных к реальному использованию.
Основные функции:

  • render(component) — монтирует компонент в виртуальный DOM
  • screen.getBy..., screen.queryBy..., screen.findBy... — поиск элементов
  • fireEvent — эмуляция событий
  • waitFor — ожидание асинхронных изменений

Пример:

test('increments counter', () => {
render(<Counter />);
fireEvent.click(screen.getByText('Увеличить'));
expect(screen.getByText('Счётчик: 1')).toBeInTheDocument();
});

Мокирование запросов

Для тестирования сетевых запросов используется msw (Mock Service Worker) или jest.mock.


Сборка и развёртывание

Create React App (CRA)

CRA предоставляет готовую среду с Webpack, Babel, ESLint.
Команды:

  • npm create react-app my-app
  • npm start — запуск dev-сервера
  • npm run build — production-сборка
  • npm test — запуск тестов

CRA скрывает конфигурацию, но её можно раскрыть через eject (необратимо).

Vite

Vite — современный сборщик с мгновенным запуском благодаря нативному ES-модулям.
Поддерживает React из коробки.
Команды:

  • npm create vite@latest my-app -- --template react
  • npm run dev
  • npm run build

Next.js

Next.js — фреймворк поверх React с SSR, SSG, File-based Routing, API Routes.
Поддерживает Server Components, Image Optimization, Middleware.
Идеален для SEO-дружественных и высокопроизводительных приложений.